/* This program is derived from the publicly licensed
 * PREDICT: A satellite tracking/orbital prediction program
 * Copyright John A. Magliacane, KD2BD 1991-2000
 *
 * [John's original licensing terms]
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License or any later
 * version.
 *
 * This program is distributed in the hope that it will useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */

/* SATCLOCK: A satellite tracking/orbital prediction program
 * intended for Amateur Radio Satellites. Hacked together in 2018
 * by Chris Baird VK2FJDA/VK2CJB <cjb@brushtail.apana.org.au>.
 */

/* Oh, GCC for the MSP430 doesn't support double...
 * Thankfully Andrew Thall's "float2" work saved the day!
 * http://andrewthall.org/papers/df64_qf128.pdf
 */

/*#define DEMOMODE*/
#define HORIZON (10)   /* only consider sats that appear above this */
#define RESOLUTION (1) /* orbit integration steps in seconds (int) */
#define KNOWNSATSONLY (0) /* only care about sats given in satinfo.h */
#define SCREENSMAX (32 * 5) /* N+1 of the sats expected to be visible */

#include <math.h>
#include <msp430.h>

/* Developed for the Olimex EasyMSP-- which has a MSP430F5510 providing
 * 'Arduino'-style header access to the GPIO ports. The '5510 can run
 * at 25 Mhz, and you'll want it that way.
 * With 32kB of Flash, 40 satellites seems to be its limit.
 */

#define cpuclk (25)
#define microsecond (cpuclk)

#define pin_RS (64)		/* ..with a 4-bit LCD Arduino shield */
#define pin_EN (128)
#define pin_d7 (32)
#define pin_d6 (16)
#define pin_d5 (8)
#define pin_d4 (4)

#define pin_beep (8)
#define pin_beepgnd (pin_beep >> 2)


/* ---------------------------------------------------------------------- */

typedef struct {
  float x;
  float y;
} float2;


void split (float2* r, float a)
{
  const float SPLIT = ((1<<12)+1);
  float t = a * SPLIT;
  float a_hi = t - (t-a);
  float a_lo = a - a_hi;
  r->x = a_hi;
  r->y = a_lo;
}


void quickTwoSum (float2 *r, float a, float b)
{
  /* iff |a| >= |b| */
  float s = a + b;
  float e = b - (s-a);
  r->x = s;
  r->y = e;
}


void twoSum (float2 *r, float a, float b)
{
  float s = a + b;
  float v = s - a ;
  float e = (a-(s-v)) + (b-v);
  r->x = s;
  r->y = e;
}


void df64add (float2 *s, float2 *a, float2 *b)
{
  float2 t;
  twoSum(s, a->x, b->x);
  twoSum(&t, a->y, b->y);
  s->y += t.x;
  quickTwoSum(s, s->x, s->y);
  s->y += t.y;
  quickTwoSum(s, s->x, s->y);
}


int df64lt (float2 *a, float2 *b)
{
  return ((b->x - a->x) + (b->y - a->y) > 0.0) ? 1 : 0;
  /* return (a->x < b->x || ((a->x == b->x) && (a->y < b->y))); */
}


/* ---------------------------------------------------------------------- */

#include "satdata.h"            /* this includes the NUMSATS define */
#include "satinfo.h"


struct {
  char  name[17];
  int   year;
  float refepoch;
  float incl;
  float raan;
  float eccn;
  float argper;
  float meanan;
  float meanmo;
  float drag;
  long  orbitnum;
} sat;


struct {
  int    status;  /* <0: disabled, 0:needs recalc, 1:valid aos info */
  float2 aos;
  int    maxel;
  int    aosaz;
  int    losaz;
  long   range;
  char*  info;
} satdetails[NUMSATS];


struct {
  float stnlat;
  float stnlong;
  int   stnalt;
} qth;


struct {
  int year;
  int month;
  int day;
  int hours;
  int minutes;
  int seconds;
} gpsdate;


#define FF (3.35289186924e-03)
#define KM (1.609344)		/* lol america */
#define R0 (6378.135)		/* radius of earth */
#define TP (6.283185307179586)
#define deg2rad (1.74532925199e-02)

float2 daynum;
float age, ak, apogee, azimuth, c0, c1, c2, c3, c7, c8, c9, c[3][2],
  df, e1, e2, e, elevation, g7, k2, l8, m1, m5, m, n0, o,
  perigee, q0, q, r3, r9, r, range, rk, s0, s1, s2, s3, s7, s8, s9,
  se, sma, t1, w, x0, x1, x5, x8, x9, x, y5, y8,
  y9, y, yone, yr, yzero, z1, z5, z8, z9, z;
/* float am, rm, vk, vm, fk, fm, ssplat, sslong; */
long irk;
int iaz, iel;


char line[80];
unsigned char gpsvalid = 0;
unsigned char numsats = 0;	/* what the GPS can see */
char datestring[]="?? DATE ??      ";
char timestring[]="?? TIME ??      ";
char satstring[]= " satellites __  ";
float2 lastfix = {0,0};


/* ---------------------------------------------------------------------- */

/* reference: http://nghiaho.com/?p=997 .. claims 0.09 degree accuracy */

float myatanf (float x)
{
#if 1 /* slow_but_accurate */
  return atanf(x) / deg2rad;
#endif

#if fast_but_slightly_dodgy
#define M_PI_6 (0.5235987755982988)
#define M_PI_12 (0.2617993877991494)
  float y;
  int complement = 0;
  int region = 0;
  int sign = 0;

  if (x < 0) { x = -x; sign = 1; }
  if (x > 1.0) { x = 1.0/x; complement = 1; }
  if (x > M_PI_12) { x = (x - M_PI_6) / (1 + M_PI_6 * x); region = 1; }

#if 1 /*this_approximation_is_about_?2deg_off_in_the_final_results*/
  float ax = fabs(x);
  y = M_PI_4*x - x*(ax-1) * (0.2447 + 0.0663*ax);
#endif
#if this_is_about_2deg_off_as_well
  float x2 = x*x;
  y = x*(1.6867629106 + x2*0.4378497304)/(1.6867633134 + x2);
#endif
  if (region) y += M_PI_6;
  if (complement) y = M_PI_2 - y;
  if (sign) y = -y;
  return y / deg2rad;
#endif
}


int mystrncmp (char* s1, char* s2, int n)
{
  while (n-- && *s1 && (*s1 == *s2))
    {
      s1++;
      s2++;
    }

  if (n == 0)
    return 0;
  else
    return (unsigned char)*s1 - (unsigned char)*s2;
}


int mystrcmp (char* s1, char* s2)
{
  for (; *s1 && (*s1 == *s2); s1++, s2++);
  return (unsigned char)*s1 - (unsigned char)*s2;
}


void mymemset (char* s, char c, int len)
{
  if (len)
    do
      *s++ = c;
    while (--len);
}


void mystrcpy (char *dst, char* src)
{
  while ((*dst++ = *src++)) ;
}


void mystrncpy (char *dst, char* src, int n)
{
  while (n-- && (*dst++ = *src++)) ;
}


int mystrlen (char* s)
{
  int n;

  for (n = 0; *s++; n++) ;
  return n;
}


void myitoa (char* p, unsigned long a, unsigned long m)
{
  int z = 0;

  while (m > 0)
    {
      *p = 48;
      while (a >= m)
	{
	  *p = *p + 1;
	  a -= m;
	}
      if ((*p == 48) && (z == 0))
	*p = 32;
      else
	z = 1;
      p++;
      m = m / 10;
    }
  if (*--p == ' ')
    *p = 48;
}


void myitoaz (char* p, unsigned long a, unsigned long m)
{
  while (m > 0)
    {
      *p = 48;
      while (a >= m)
	{
	  *p = *p + 1;
	  a -= m;
	}
      p++;
      m = m / 10;
    }
}


void myftoa (char* p, float f, int d)
{
  float m, t;

  if (f < 0)
    {
      *p++ = '-';
      f = -f;
    }

  m = 1.0;
  t = 1.0;
  while (m < f)
    {
      t = m;
      m *= 10.0;
    }
  m = t;

  while (d >= 0)
    {
      *p = 48;
      while (f >= m)
	{
	  *p = *p + 1;
	  f -= m;
	}
      p++;
      if ((int)m == 1)
	*p++ = '.';
      if ((int)m <= 1)
	d--;
      m = m / 10;
    }
}


long myatoi (char *s)
{
  long r = 0;
  char neg = 0;

  if (*s == '-')
    {
      s++;
      neg = 1;
    }

  for (; *s; s++)
    {
      if ((*s >= '0') && (*s <= '9'))
        r = (r<<3) + (r<<1) + *s - '0';
    }

  if (neg) r = -r;
  return r;
}


/* ---------------------------------------------------------------------- */

void longdelay (unsigned int i)
{
  while (i--) __delay_cycles(cpuclk*1000);
}


/* SetVcoreUp and Clk25MHz were ripped from TI sources.. */

void SetVcoreUp (unsigned int level)
{
  PMMCTL0_H = PMMPW_H;
  SVSMHCTL = SVSHE + SVSHRVL0 * level + SVMHE + SVSMHRRL0 * level;
  SVSMLCTL = SVSLE + SVMLE + SVSMLRRL0 * level;
  while ((PMMIFG & SVSMLDLYIFG) == 0);
  PMMIFG &= ~(SVMLVLRIFG + SVMLIFG);
  PMMCTL0_L = PMMCOREV0 * level;
  if ((PMMIFG & SVMLIFG))
    while ((PMMIFG & SVMLVLRIFG) == 0);
  SVSMLCTL = SVSLE + SVSLRVL0 * level + SVMLE + SVSMLRRL0 * level;
  PMMCTL0_H = 0x00;
}


void Clk25MHz (void)
{
  SetVcoreUp(1);
  SetVcoreUp(2);
  SetVcoreUp(3);

  UCSCTL3 |= SELREF_2;		/* Set DCO FLL reference = REFO */
  UCSCTL4 |= SELA_2;		/* Set ACLK = REFO */
  __bis_SR_register(SCG0);	/* Disable the FLL control loop */
  UCSCTL0 = 0x0000;		/* Set lowest possible DCOx, MODx */
  UCSCTL1 = DCORSEL_7;		/* Select DCO range 50MHz operation */
  /* DCO Multiplier for 25MHz:
   * Fdco = (N + 1) * FLLRef = (762 + 1) * 32768 = 25 Mhz
   * FLL Div = fDCOCLK/2 */
  UCSCTL2 = FLLD_1 + 762;
  __bic_SR_register(SCG0);	/* Enable the FLL control loop */

  /* 32 x 32 x 25 MHz / 32,768 Hz ~ 780k MCLK cycles for DCO to settle */
  __delay_cycles(782000);

  /* Loop until XT1,XT2 & DCO stabilizes */
  do {
    UCSCTL7 &= ~(XT2OFFG+XT1LFOFFG+DCOFFG);
    SFRIFG1 &= ~OFIFG;		/* Clear XT2,XT1,DCO fault flags */
  } while (SFRIFG1 & OFIFG);	/* Test oscillator fault flag */
}


void configure_430 (void)
{
  WDTCTL = WDTPW + WDTHOLD;

  /* LCD IO */
  P1DIR = pin_RS | pin_EN | pin_d7 | pin_d6 | pin_d5 | pin_d4;

  P4DIR |= pin_beep | pin_beepgnd; /* beeper */

  PJDIR |= 2;	/* power-up the GPS module -- set UEXT_PWR_E (PJ_1) */
  PJOUT &= ~2;

  Clk25MHz();

  /* USCI A1 UART setup */
  P4SEL |= BIT4 | BIT5;		/* P4.4,5 = UART1 TXD/RXD */
  UCA1CTL1 |= UCSWRST;		/* Put state machine in reset mode */
  UCA1CTL1 |= UCSSEL_1;		/* ACLK */
  UCA1BR0 = 3;
  UCA1BR1 = 0;
  UCA1MCTL = UCBRS_3+UCBRF_0;
  UCA1CTL0 = 0;
  UCA1CTL1 &= ~UCSWRST;		/* initialize USCI state machine */
  UCA1IE |= UCRXIE;		/* enable USCI_A1 RX interrupt */
  __bis_SR_register(GIE);
}


/* ---------------------------------------------------------------------- */

long DayNum (int m, int d, int y)
{
  /* calculate the day number from m/d/y */
  long dn;
  float mm, yy;

  if (m < 3)
    {
      y--;
      m += 12;
    }

  if (y <= 50) y += 100;        /* Correct for Y2K... */

  yy = (float)y;
  mm = (float)m;
  dn = (long)(floorf(365.25 * (yy - 80.0)) - floorf(19.0 + yy / 100.0) +
	      floorf(4.75 + yy / 400.0) - 16.0);
  dn += d + 30*m + (long)floorf(0.6 * mm - 0.3);
  return dn;
}


void CurrentDaynum ()
{
  float day, d;
  float2 temp1, temp2;

  day = DayNum (gpsdate.month, gpsdate.day, gpsdate.year);
  d = (float)gpsdate.seconds/(float)86400.0;
  d += (float)gpsdate.minutes/(float)1440.0;
  d += (float)gpsdate.hours/(float)24.0;
  split(&temp1, day);
  split(&temp2, d);
  df64add(&daynum, &temp1, &temp2);
}


int AosHappens ()
{
  /* This function returns a 1 if the satellite can ever
   * rise above the horizon of the ground station */
  float lin, sma, apogee;

  lin = sat.incl;
  if (lin >= 90.0) lin = 180.0 - lin;

  sma = 331.25 * powf(1440.0/sat.meanmo, 2.0/3.0);
  //sma = 331.25 * expf(logf (1440.0 / sat.meanmo) * (2.0/3.0));
  apogee = sma * (1.0+sat.eccn) - R0;

  if ((acosf(R0 / (apogee + R0)) + (lin*deg2rad))
      > fabs((qth.stnlat+HORIZON) * deg2rad))
    return 1;
  else
    return 0;
}


int Decayed ()
{
  /* This function returns a 1 if it appears that the current satellite has
   * decayed at the time of 'daynum' */
  float2 nsatepoch;
  float2 r;
  float t;

  t = (16.666666 - sat.meanmo) / (10.0 * fabs(sat.drag));
  t += sat.refepoch;
  twoSum(&nsatepoch, -DayNum(1, 0, sat.year), -t);
  df64add(&r, &daynum, &nsatepoch);

  if (r.x > 0)
    return 1;
  else
    return 0;
}


int Geostationary ()
{
  /* This function returns a 1 if the current satellite appears to be in a
   * geostationary orbit */

  if (fabs(sat.meanmo - 1.0027) < 0.0002)
    return 1;
  else
    return 0;
}


void LoadKep (int i)
{
  mystrncpy (sat.name, satdata[i].name, 16);
  sat.year = satdata[i].year;
  sat.refepoch = satdata[i].refepoch;
  sat.drag = satdata[i].drag;
  sat.incl = satdata[i].incl;
  sat.raan = satdata[i].raan;
  sat.eccn = satdata[i].eccn;
  sat.argper = satdata[i].argper;
  sat.meanan = satdata[i].meanan;
  sat.meanmo = satdata[i].meanmo;
  sat.orbitnum = satdata[i].orbitnum;
}


void PreCalcSat (int satnum)
{
#define kFF (1.0-FF)*(1.0-FF)
  float xx, xxt;

  LoadKep(satnum);

  /* epoch = DayNum(1, 0, sat.year) + sat.refepoch; */
  age = ((daynum.x - DayNum(1, 0, sat.year)) - sat.refepoch) + daynum.y;

  yr = (float)sat.year;
  if (yr <= 50.0) yr += 100.0;  /* Y2K... */

  t1 = yr - 1.0;
  df = 366.0 + floorf(365.25 * (t1 - 80.0)) -
    floorf(t1 / 100.0) + floorf(t1 / 400.0 + 0.75);
  t1 = (df + 29218.5) / 36525.0;
  t1 = 6.6460656 + t1*(2400.051262 + t1*2.581e-5);
  se = t1 / 24.0 - yr;
  n0 = sat.meanmo + (age * sat.drag);
  sma = 331.25 * powf((1440.0 / n0), (2.0 / 3.0));
  e2 = 1.0 - (sat.eccn * sat.eccn);
  e1 = sqrtf(e2);
  k2 = 9.95 * powf(R0 / sma, 3.5) / e2 * e2;
  s1 = sinf(sat.incl * deg2rad);
  c1 = cosf(sat.incl * deg2rad);
  l8 = qth.stnlat * deg2rad;
  s9 = sinf(l8);
  c9 = cosf(l8);
  s8 = sinf(-qth.stnlong * deg2rad);
  c8 = cosf(qth.stnlong * deg2rad);
  r9 = R0 * (1.0 - (FF/2.0) * (cosf(2.0*l8) - 1.0)) - (qth.stnalt/1000.0);
  xx = kFF * s9 / c9;
  xxt = r9 / sqrtf(1 + xx*xx);	/* sin(atan(x)) = x/((1+x^2)^-2) */
  z9 = xxt * xx;
  x9 = xxt * c8;
  y9 = xxt * s8;
  apogee = sma * (1.0 + sat.eccn) - R0;
  perigee = sma * (1.0 - sat.eccn) - R0;
}


void CalcSat (void)
{
  age = ((daynum.x - DayNum(1, 0, sat.year)) - sat.refepoch) + daynum.y;
  o = deg2rad * (sat.raan - age * k2 * c1);
  s0 = sinf(o);
  c0 = cosf(o);
  w = deg2rad * (sat.argper + age * k2 * (2.5 * c1 * c1 - 0.5));
  s2 = sinf(w);
  c2 = cosf(w);
  c[0][0] = (c2*c0) - (s2*s0*c1);
  c[1][0] = (c2*s0) + (s2*c0*c1);
  c[0][1] = (-s2*c0) - (c2*s0*c1);
  c[1][1] = (-s2*s0) + (c2*c0*c1);
  c[2][0] = s2*s1;
  c[2][1] = c2*s1;
  q0 = (sat.meanan / 360.0) + sat.orbitnum;
  q = n0 * age + q0;
  q = q - floorf(q);
  m = q * TP;
  e = m + sat.eccn * (sinf(m) + 0.5*sat.eccn * sinf(m*2.0));

  do                            /* Kepler's Equation */
    {
      s3 = sinf(e);
      c3 = cosf(e);
      r3 = 1.0 - (sat.eccn * c3);
      m1 = e - (sat.eccn * s3);
      m5 = m1 - m;
      e = e - m5 / r3;
    }
  while (fabs(m5) >= 1.0e-6);

  x0 = sma * (c3 - sat.eccn);
  yzero = sma * e1 * s3;
  r = sma * r3;
  x1 = (x0 * c[0][0]) + (yzero * c[0][1]);
  yone = (x0 * c[1][0]) + (yzero * c[1][1]);
  z1 = (x0 * c[2][0]) + (yzero * c[2][1]);
  g7 = ((daynum.x - df) + daynum.y) * 1.0027379093 + se;
  g7 = TP * (g7 - floorf(g7));
  s7 = -sinf(g7);
  c7 = cosf(g7);
  x = (x1 * c7) - (yone * s7);
  y = (x1 * s7) + (yone * c7);
  z = z1;
  x5 = x - x9;
  y5 = y - y9;
  z5 = z - z9;
  range = (x5*x5) + (y5*y5) + (z5*z5);
  z8 = (x5*c8*c9) + (y5*s8*c9) + (z5*s9);
  x8 = (-x5*c8*s9) - (y5*s8*s9) + (z5*c9);
  y8 = (y5*c8) - (x5*s8);
  ak = r - R0;
  rk = sqrtf(range);
  elevation = myatanf(z8 / sqrtf(range - z8*z8));
  azimuth = myatanf(y8 / x8);
  if (x8 < 0.0) azimuth += 180.0;
  if (azimuth < 0.0) azimuth += 360.0;

#if dopplercode || mercanunits
  am = ak / KM;
  rm = rk / KM;
  vk = 3.6*sqrtf(3.98652e+14 * ((2.0 / (r * 1000.0)) - 1.0 / (sma * 1000.0)));
  vm = vk / KM;
  fk = 12756.33 * acosf(R0 / r);
  fm = fk / KM;
  ssplat = myatanf(z / sqrtf(r*r - z*z));
  ssplong = -myatanf(y / x);
  if (x < 0.0) ssplong += 180.0;
  if (ssplong < 0.0) ssplong += 360.0;
#endif

  /* ..the long-awaited results.. */
  irk = (long)rint(rk);
  iaz = (int)rint(azimuth);
  iel = (int)rint(elevation);
}


/* ---------------------------------------------------------------------- */

void GpsParse()
{
  char* p;
  char* last;
  char* lp;
  char* tokens[20];
  int i;

  for (p = line; *p; p++)
    if (*p < 32)
      *p = 0;
  last = p;

  /* a strtok which recognizes 'null' elements */
  i = 0;
  lp = line;
  for (p = line; p < last; p++)
    if (*p == ',')
      {
        tokens[i++] = lp;
        lp = p+1;
        *p = 0;
      }
  tokens[i] = 0;

  /* */

  if (mystrncmp("$GPGGA", line, 6) == 0)
    {
      numsats = myatoi(tokens[7]);
      if (gpsvalid)
	{
	  qth.stnalt = myatoi(tokens[9]) - myatoi(tokens[11]);
	  if (qth.stnalt < 0)
	    qth.stnalt = 1;
	}
    }

  if (mystrncmp("$GPRMC", line, 6) == 0)
    {
      if (*tokens[2] == 'A')
	gpsvalid = 1;
      else
        gpsvalid = 0;

      mystrncpy(timestring, "?? TIME ??      ", 16);
      if ((*tokens[1] >= '0') && (*tokens[1] <= '9'))
        {
          timestring[0] = *tokens[1];
          timestring[1] = *(tokens[1]+1);
          timestring[2] = ':';
          timestring[3] = *(tokens[1]+2);
          timestring[4] = *(tokens[1]+3);
          timestring[5] = ':';
          timestring[6] = *(tokens[1]+4);
          timestring[7] = *(tokens[1]+5);
          timestring[8] = 'Z';
          timestring[9] = ' ';
          timestring[10] = ' ';
          timestring[11] = ' ';

          *(tokens[1]+6) = 0;
          gpsdate.seconds = myatoi(tokens[1]+4);
          *(tokens[1]+4) = 0;
          gpsdate.minutes = myatoi(tokens[1]+2);
          *(tokens[1]+2) = 0;
          gpsdate.hours = myatoi(tokens[1]);
        }

      mystrncpy(datestring, "?? DATE ??      ", 16);
      if ((*tokens[9] >= '0') && (*tokens[9] <= '9'))
        {
          datestring[0] = '2';
          datestring[1] = '0';
          datestring[2] = *(tokens[9]+4);
          datestring[3] = *(tokens[9]+5);
          datestring[4] = '-';
          datestring[5] = *(tokens[9]+2);
          datestring[6] = *(tokens[9]+3);
          datestring[7] = '-';
          datestring[8] = *tokens[9];
          datestring[9] = *(tokens[9]+1);
          datestring[10] = ' ';
          datestring[11] = ' ';

          gpsdate.year = myatoi(tokens[9]+4);
          *(tokens[9]+4) = 0;
          gpsdate.month = myatoi(tokens[9]+2);
          *(tokens[9]+2) = 0;
          gpsdate.day = myatoi(tokens[9]);
        }

      long lat = myatoi(tokens[3]);
      long lon = myatoi(tokens[5]);

      int dd = (float)lat/10000000.0;
      float ss = lat - dd*10000000;
      float flat = (float)dd + ss/6000000.0;

      dd = (float)lon/10000000.0;
      ss = lon - dd*10000000;
      float flon = (float)dd + ss/6000000.0;

      if (*tokens[4] == 'S')
        flat = -flat;
      if (*tokens[6] == 'W')
        flon = -flon;

      if (gpsvalid)
        {
          qth.stnlat = flat;
          qth.stnlong = -flon;
        }

      satstring[12] = 48 + (numsats/10);
      satstring[13] = 48 + (numsats % 10);
    }
}


#define UARTQUEUE (32)		/* 2^N */
unsigned char rxqueue[UARTQUEUE];
volatile int rxhead = 0;	/* volatile needed!!! */
volatile int rxtail = 0;

#include <isr_compat.h>

ISR(USCI_A1, USCI_A1_ISR)
{
  rxqueue[rxhead++] = UCA1RXBUF;
  rxhead &= (UARTQUEUE-1);
}


unsigned char uart_getc (void)
{
  unsigned char c;

  while (rxtail == rxhead) ;
  c = rxqueue[rxtail++];
  rxtail &= (UARTQUEUE-1);
  return c;
}


void ReadGPS ()
{
#ifndef DEMOMODE

  char* p;
  while (1)
    {
      for (p = line; ((p-line) < 80) & ((*p=uart_getc()) != 10); p++) ;
      GpsParse();
      if (mystrncmp("$GPRMC", line, 6) == 0)
	break;
    }

#endif
#ifdef DEMOMODE

  static long clock = (3 * 3600) + (70 * 60) + 30;

  gpsvalid = 0;
  if (clock > 10)
    gpsvalid = 1;

  qth.stnlat = -32.92469;
  qth.stnlong = -151.65434;
  qth.stnalt = 100;
  gpsdate.year = 18;
  gpsdate.month = 3;
  gpsdate.day = 11;
  gpsdate.hours = (clock / 3600);
  gpsdate.minutes = (clock / 60) % 60 ;
  gpsdate.seconds = clock % 60;
  numsats = 33;
  clock++;

  longdelay(500);

#endif
}


/* ---------------------------------------------------------------------- */

void lcdWriteNybble (unsigned char rs, unsigned char nybble)
{
  unsigned char p = nybble << 2;

  P1OUT = p | rs;
  P1OUT = p | rs | pin_EN;
  __delay_cycles(microsecond >> 1);	/* at least 450ns */
  P1OUT = p | rs;
  __delay_cycles(microsecond >> 1);
}


void lcdWriteCmd (unsigned char cmd)
{
  /* command: RS=0 */
  lcdWriteNybble(0, cmd >> 4);
  lcdWriteNybble(0, cmd & 15);
  __delay_cycles(37 * microsecond);
}


void lcdWriteData (unsigned char byte)
{
  /* data: RS=1 */
  lcdWriteNybble(pin_RS, byte >> 4);
  lcdWriteNybble(pin_RS, byte & 15);
  __delay_cycles(37 * microsecond);
}


void lcdClear (void)
{
  lcdWriteCmd(0x01);
  __delay_cycles((unsigned int)2000 * microsecond); /* 2ms */
}


unsigned char udgs[32] =
  {
   0b00000,0b00100,0b01110,0b11011,0b01110,0b00100,0b00000,0b00000, /* compass*/
   0b00000,0b00010,0b00010,0b00100,0b00100,0b01000,0b01111,0b00000, /* angle */
   0b00000,0b00000,0b11111,0b01110,0b00100,0b00000,0b00000,0b00000,
   0b00000,0b00000,0b00100,0b01110,0b11111,0b00000,0b00000,0b00000,
  };


void lcdReset (void)
{
  int i;

  lcdWriteCmd(0x20);
  __delay_cycles((unsigned int)4100 * microsecond); /* >4.1ms */
  lcdWriteCmd(0x20);
  __delay_cycles(100 * microsecond); /* >100us */
  lcdWriteCmd(0x20);

  lcdWriteCmd(0x28);		/* 4 bit interface, 2 lines, 5x7 font */
  lcdWriteCmd(0x0c);		/* Display on, no cursor */
  lcdWriteCmd(0x06);		/* Auto-increment, no shifting */

  lcdWriteCmd(0x48);
  for (i = 0; i < 32; i++)	/* user-defined characters.. */
    lcdWriteData(udgs[i]);

  lcdClear();
}


void lcdSetpos (unsigned char x, unsigned char y)
{
  lcdWriteCmd((y == 0 ? 0x80 : 0xC0) + (x & 63));
}


void lcdScreen (char* s)
{
  unsigned char i;

  lcdSetpos(0,0);
  for (i = 16; i; i--)
    lcdWriteData(*s++);
  lcdSetpos(0,1);
  for (i = 16; i; i--)
    lcdWriteData(*s++);
}


void lcdPuts (char* s)
{
  while (*s)
    lcdWriteData(*s++);
}


/* ---------------------------------------------------------------------- */

void beep (int i)
{
  P4OUT = (P4OUT & ~pin_beepgnd) | pin_beep;
  longdelay(i<<7);
  P4OUT &= ~pin_beep;
  longdelay(128);
}


void aosmorse (void)
{
  beep(1); beep(3);
  longdelay(256);
  beep(3); beep(3); beep(3);
  longdelay(256);
  beep(1); beep(1); beep(1);
}


void emorse (void)
{
  beep(1);
}


/* ---------------------------------------------------------------------- */

#define min(a,b) (a < b ? a : b)


void FixSatDetails ()
{
  int i, j;

  for (j = 0; j < NUMSATS; j++)
    {
      satdetails[j].status = -KNOWNSATSONLY;
      satdetails[j].aos.x = 0;
      satdetails[j].aos.y = 0;
      satdetails[j].maxel = 0;
      satdetails[j].range = 100000;
      satdetails[j].info = (char*)0;
      for (i = 0; satinfo[i].name != (char*)0; i++)
        if (mystrcmp(satdata[j].name, satinfo[i].name) == 0)
          {
            satdetails[j].info = satinfo[i].info;
            satdetails[j].status = 0;
	    break;
          }
    }
}


void ReCalc (int i)
{
  unsigned int satpass = 0;
  float2 aos;
  int maxel = -1000;
  unsigned int aosaz = 0;
  unsigned int losaz = 0;
  unsigned char ss=0, ms=0, hs=0;
  static char decstr[] = "00h00";
  static float2 dftemp;
  static float2 tick;
  int skip;

  lcdClear();
  lcdPuts("Calc orbit");
  lcdSetpos(0,1);
  lcdPuts(satdata[i].name);

  PreCalcSat(i);
  aos.x = 0; aos.y = 0;
  split(&tick, RESOLUTION/86400.0);

  while (++satpass)
    {
      CalcSat();

      if (iel > maxel)		/* might be off? */
	maxel = iel;

      if ((iel >= HORIZON) && (aos.x == 0)) /* AOS found */
	{
	  aos.x = daynum.x; aos.y = daynum.y;
	  aosaz = iaz;
	}

      if ((iel < HORIZON-5) && aos.x) /* LOS found */
	{
	  losaz = iaz;
	  break;	/* can quit now */
	}

      if ((satpass & 15) == 0)
	{
	  myitoa(decstr, hs, 10);
	  myitoaz(decstr+3, ms, 10);
	  lcdSetpos(11,0);
	  lcdPuts(decstr);

	  lcdSetpos(12,1);
	  if (iel < 0)
	    {
	      lcdWriteData('-');
	      iel = -iel;
	    }
	  else
	    lcdWriteData('+');
	  myitoaz(decstr+3, iel, 10);
	  lcdPuts(decstr+3);
	  lcdWriteData(223);	/* degree symbol */
	}

      /* if iel below horizon, use bigger time skip */

      skip = 1;
      if (iel < -4)
	skip = -iel >> 2;

      while (skip--)
	{
	  df64add(&dftemp, &daynum, &tick);
	  daynum.x = dftemp.x; daynum.y = dftemp.y;
	  ss += RESOLUTION;
	}

      while (ss >= 60)
	{
	  ss -= 60;
	  ms++;
	}

      while (ms >= 60)
	{
	  ms -= 60;
	  hs++;
	}
    }

  satdetails[i].aos.x = aos.x;
  satdetails[i].aos.y = aos.y;
  satdetails[i].maxel = maxel;
  satdetails[i].aosaz = aosaz;
  satdetails[i].losaz = losaz;
  satdetails[i].range = 100000;
  satdetails[i].status = 1;
}


/* ---------------------------------------------------------------------- */

void main ()
{
  static char display[SCREENSMAX+1];
  static char waitstr[]="WAITING FOR GPS WAITING FOR GPS ";
  int i, redoi, aosi;
  unsigned char checksats = 0;
  unsigned int pass = 0;
  unsigned long tickpass = 0;
  unsigned int v;
  float2 aos;
  static unsigned int vissats;
  static float2 dftemp;
  static float2 df64_5min;

  split(&df64_5min, 300.0/86400.0);

  /* */

  configure_430();
  lcdReset();
  FixSatDetails();
  lcdPuts("SatClock430 1.0");
  lcdSetpos(0,1);
  lcdPuts("  VK2CJB 2019");
  //lcdPuts("  VK2FJDA 2018");
  emorse();
  longdelay(1000);
  lcdClear();

  /* */

  while (1)
    {
      pass++;
      ReadGPS();

      /* spin here if nothing from the GPS yet */

      if ((gpsvalid == 0) && lastfix.x == 0)
        {
          display[32] = 0;
          mystrncpy(display, waitstr+(pass&15), 16);
          mymemset(display+33, 32, 48);
          mystrncpy(display+36, datestring, 10);
          mystrncpy(display+52, timestring, 10);
          mystrncpy(display+65, satstring, 15);
          mystrncpy(display+16, display+33+(((pass>>2)%3)<<4), 16);
          lcdScreen(display);
          continue;		/* restart the while loop */
        }

      /* */

      CurrentDaynum();

      if (gpsvalid)
	{
	  lastfix.x = daynum.x;
	  lastfix.y = daynum.y;
	  tickpass = 0;
	}
      else
	{
	  tickpass++;
	  split(&dftemp, (float)tickpass/86400.0);
	  df64add(&daynum, &lastfix, &dftemp);
	}

      /* something to do when time & location details are known */

      if (checksats == 0)
        {
          checksats = 1;
          for (i = 0; i < NUMSATS; i++)
            {
              LoadKep(i);
              if (Geostationary() || Decayed() || !AosHappens())
		satdetails[i].status = -1;
            }
        }

      /* scan sats for visibility and aos */

      vissats = 0;
      aos.x = daynum.x + 1.0; aos.y = daynum.y;
      aosi = -1;
      redoi = -1;
      v = 0;

      for (i = 0; i < NUMSATS; i++)
        {
	  if (satdetails[i].status < 0) /* eliminated sats */
            continue;

	  /* check if recalc needed -- it'll eventually get done, even
	   * if later sats set redoi too */

	  if (satdetails[i].status == 0)
	    redoi = i;

	  /* check if this has an earlier (and valid) aos than others */

	  if (satdetails[i].aos.x &&
	      df64lt(&daynum, &satdetails[i].aos) &&
	      df64lt(&satdetails[i].aos, &aos))
            {
              aosi = i;
              aos.x = satdetails[i].aos.x;
              aos.y = satdetails[i].aos.y;
            }

	  /* if aos calculated and > daynum, skip the rest */

	  if (satdetails[i].aos.x && df64lt(&daynum, &satdetails[i].aos))
	    continue;

	  /* check if visible and append its details if so */

	  PreCalcSat(i);
	  CalcSat();

	  if ((iel >= HORIZON) && (v < SCREENSMAX))
	    {
	      vissats = 1;
	      satdetails[i].status = 0;
	      if (satdetails[i].aos.x) /* first time here-- beep */
		aosmorse();
	      satdetails[i].aos.x = 0; /* disappear it from other checks */

	      mystrcpy(display+v, "          __x___                ");
	      myitoa(display+v+10, iel, 10);
	      myitoaz(display+v+13, iaz, 100);
	      mystrncpy(display+v, satdata[i].name,
			min(9,mystrlen(satdata[i].name)));
	      if (irk <= satdetails[i].range)
		{
		  display[v+12] = 3; /* "range is less-than before" */
		  satdetails[i].range = irk;
		}
	      else		   /* i.e: getting closer */
		display[v+12] = 4;

	      /* flip through the information text if present */
	      if (satdetails[i].info)
		{
		  int l = (pass & 248) << 1;
		  int t = l % mystrlen(satdetails[i].info);
		  mystrncpy(display+v+16, satdetails[i].info + t, 16);
		}
	      v += 32;
	    }
        }

      /* append the next AOS info to display (if there is one..) */

      if (!vissats && (aosi >= 0) && (v < SCREENSMAX))
	{
	  float d =
	    1440.0*(satdetails[aosi].aos.y - daynum.y) +
	    1440.0*(satdetails[aosi].aos.x - daynum.x);
	  int dm = d;
	  int ds = 60.0*(d - (float)dm);

	  mystrcpy(display+v, "Next            ____m__  ");
	  switch ((pass >> 2) % 3)
	    {
	    case 0:
	      mystrcpy(display+v+25, "Max ??\337");
	      myitoa(display+v+29, satdetails[aosi].maxel, 10);
	      break;
	    case 1:
	      mystrcpy(display+v+25, "AOS\001???");
	      myitoaz(display+v+29, satdetails[aosi].aosaz, 100);
	      break;
	    case 2:
	      mystrcpy(display+v+25, "LOS\001???");
	      myitoaz(display+v+29, satdetails[aosi].losaz, 100);
	      break;
	    }
	  mystrncpy(display+v+5, satdata[aosi].name,
		    min(11,mystrlen(satdata[aosi].name)));
	  static char tmptext[] = "____m__    ";
	  if (dm < 0)
	    dm = 9999, ds = 99;
	  myitoa(tmptext, dm, 1000);
	  myitoaz(tmptext+5, ds, 10);
	  char* tt = tmptext;
	  if (dm < 1000) tt++;
	  if (dm < 100) tt++;
	  if (dm < 10) tt++;
	  mystrncpy(display+v+16, tt, 7);
	  if (gpsvalid)
	    display[v+23] = (pass & 1) ? '*' : '+';
	  v += 32;
	}

      /* include location details if there's nothing better to do */

      df64add(&dftemp, &daynum, &df64_5min);
      i = df64lt(&aos, &dftemp); /* i is used later */

#if 0
      if (!vissats && !i && (v < SCREENSMAX))
	{
	  mymemset(display+v, 32, 32);
	  myftoa(display+v+3, -qth.stnlat, 6);
	  myftoa(display+v+18, -qth.stnlong, 6);
	  if (gpsvalid == 0)
	    display[v+31] = (pass & 1) ? '*' : '+';
	  else
	    {
	      myitoaz(display+v+30, numsats, 10);
	      display[v+29] = 35;
	    }
	  display[v] = qth.stnlat < 0 ? 'S' : 'N';
	  display[v+16] = qth.stnlong < 0 ? 'E' : 'W';
	  v += 32;
	}
#endif

      /* Recalcuate at the program start, or after a sat has passed */
      /* if <5m to next AOS, or sat visible, don't calc */

      if (!vissats && !i && (redoi >= 0))
	ReCalc(redoi);

      /* pick which screen from display[] to show */

      if (v)
	{
	  v = (pass >> 3) % (v >> 5);
	  v  = v << 5;
	  lcdScreen(display+v);
	}
    }
}

/* ---------------------------------------------------------------------- */
